/* Includes ------------------------------------------------------------------*/
#include "i2c_dev_lst.h"
#include "m41t6x.h"
#include "time.h"
#include "esp_log.h"
#include "string.h"

/* Private macro -------------------------------------------------------------*/
// 1 move x bits to the left
#define bit(x) (1 << (x))

/* Private typedef -----------------------------------------------------------*/
// m41t6x reg address
typedef enum
{
    REG_TIME_ADR = 0x00,
    REG_ST_ADR = 0x01,
    REG_OFIE_ADR = 0x02,
    REG_SQW_ADR = 0x04,
    REG_OUTPUT_ADR = 0x08,
    REG_CALIBRATION_ADR = 0x08,
    REG_WTD_ADR = 0x09,
    REG_AFE_ADR = 0x0A,
    REG_SQWE_ADR = 0x0A,
    REG_RPT_ADR = 0X0B,
    REG_WDF_ADR = 0x0F,
    REG_AF_ADR = 0x0F,
    REG_OF_ADR = 0x0F,
    REG_ALARM_ADR = 0x0A,
} reg_addr_t;

// m41t6x cmd
typedef enum
{
    M41CMD_STOP = 0x80,
    M41CMD_OSC_STOP = 0x80,
    M41T6X_SQW_SET = 0x0F,
    M41T6X_SQW_GET = 0xF0,
    M41T6X_OUT = 0x80,
    M41T6X_CALIBRATION = 0x80,
    M41T6X_AFE_SET = 0x80,
    M41T6X_SQWE_SET = 0x40,
    M41T6X_OF_SET = 0x04,
} m41t6x_cmd_t;

// century
typedef enum
{
    M41T6X_CENTURY_0 = 0x00, // 20
    M41T6X_CENTURY_1 = 0x40, // 21
    M41T6X_CENTURY_2 = 0X80, // 22
    M41T6X_CENTURY_3 = 0xC0, // 23
} m41t6x_century_t;

/* Private variables ---------------------------------------------------------*/
// m41t6x tag
const char *TAG = "m41t6x";

// point to m41t6x_t
m41t6x_handle m41t6x_p = NULL;

/* Private function prototypes -----------------------------------------------*/

// utils functions

uint8_t bcd8_to_dec(bcd8_t bcd);
uint8_t dec_to_bcd8(uint8_t dec);

// write and read regs
esp_err_t m41t6x_read_regs(uint8_t reg_adr, byte_t *out_buf, size_t dlen);
esp_err_t m41t6x_write_regs(uint8_t reg_adr, byte_t *in_buf, size_t dlen);
// control m41t6x time
esp_err_t set_rtc_time(rtc_time_t *time);
esp_err_t get_rtc_time(rtc_time_t *time);
esp_err_t sync_time(void);
// control oscillator
esp_err_t m41t6x_oscillator_enable_set(bit_t status);
esp_err_t m41t6x_oscillator_enable();
esp_err_t m41t6x_oscillator_disable();
esp_err_t m41t6x_oscillator_enable_get(bit_t *status);
esp_err_t get_oscillator_status(bit_t *status);
esp_err_t set_rtc_of(bit_t of);
esp_err_t m41t6x_crystal(bit_t status);
esp_err_t m41t6x_run(void);
esp_err_t m41t6x_stop(void);
esp_err_t m41t6x_status_get(bit_t *status);
// control output level
esp_err_t get_output_level(bit_t *out_level);
esp_err_t set_output_level(bit_t out_level);
// control sqw
esp_err_t set_rtc_sqw(m41t6x_sqw_t sqw);
esp_err_t get_rtc_sqw(m41t6x_sqw_t *sqw);
esp_err_t get_sqw_status(bit_t *sqwe);
esp_err_t sqw_open(void);
esp_err_t sqw_close(void);
esp_err_t set_sqwe(bit_t sqwe);
// control calibration
esp_err_t set_rtc_calibration(uint8_t calibration);
esp_err_t get_rtc_calibration(uint8_t *calibration);
// control watchdog
esp_err_t set_wtd(uint8_t wtd_mul, wtd_res_t wtd);
esp_err_t get_wtd(uint8_t *wtd_mul, wtd_res_t *wtd);
esp_err_t get_wtd_status(bit_t *wdf);
// control alarm
esp_err_t get_rtc_af(bit_t *af);
esp_err_t set_rtc_afe(bit_t afe);
esp_err_t enable_af(void);
esp_err_t disable_af(void);
esp_err_t get_rtc_afe(bit_t *afe);
esp_err_t get_alarm(rtc_time_t *alarm);
esp_err_t set_alarm(rtc_time_t alarm);
esp_err_t alarm_delete(void);
esp_err_t set_alarm_repeat_mode(m41t6x_rpt_t rpt);
esp_err_t get_alarm_repeat_mode(m41t6x_rpt_t *rpt);

/* Private functions ---------------------------------------------------------*/

/*******************************************************************************
 * Function Name : m41t6x_init
 * Function      : init m41t6x
 * Input         : [m41t6x_handle *dhandle, i2c_dev_lst_t *i2c_dev_lst]
 *                  * dhandle:       m41t6x handle, point to m41t6x_t
 *                  * i2c_dev_lst:   i2c devices list
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_init(m41t6x_handle *dhandle,
                      i2c_dev_lst_t *i2c_dev_lst,
                      m41t6x_transmit_handle write,
                      m41t6x_transmit_handle read)
{
    *dhandle = malloc(sizeof(m41t6x_t));
    memset(*dhandle, 0, sizeof(m41t6x_t));
    m41t6x_p = *dhandle;

    if (i2c_dev_lst == NULL)
    {
        ESP_LOGE("m41t6x", "I2C device list is NULL");
        return ESP_ERR_INVALID_ARG;
    }
    i2c_dev_lst_t *i2c_dev = i2c_dev_list_find(i2c_dev_lst, M41T6X_ADR);
    if (i2c_dev != NULL)
    {
        (*dhandle)->i2c.dev = i2c_dev;
    }
    else
    {
        ESP_LOGW("m41t6x", "m41t6x at address 0x%02x not found", M41T6X_ADR);
    }

    m41t6x_p->time_get = get_rtc_time;
    m41t6x_p->time_set = set_rtc_time;
    m41t6x_p->sync_time = sync_time;

    m41t6x_p->osc.intr_get = m41t6x_oscillator_enable_get;
    m41t6x_p->osc.intr_enable = m41t6x_oscillator_enable;
    m41t6x_p->osc.intr_disable = m41t6x_oscillator_disable;
    m41t6x_p->osc.failed_get = get_oscillator_status;
    m41t6x_p->osc.failed_set = set_rtc_of;
    m41t6x_p->osc.run = m41t6x_run;
    m41t6x_p->osc.stop = m41t6x_stop;
    m41t6x_p->osc.status_get = get_oscillator_status;

    m41t6x_p->out.level_set = set_output_level;
    m41t6x_p->out.level_get = get_output_level;

    m41t6x_p->cal.set = set_rtc_calibration;
    m41t6x_p->cal.get = get_rtc_calibration;

    m41t6x_p->wtd.set = set_wtd;
    m41t6x_p->wtd.get = get_wtd;
    m41t6x_p->wtd.status_get = get_wtd_status;

    m41t6x_p->alarm.afe_get = get_rtc_afe;
    m41t6x_p->alarm.enable_af = enable_af;
    m41t6x_p->alarm.disable_af = disable_af;
    m41t6x_p->alarm.repeat_mode_get = get_alarm_repeat_mode;
    m41t6x_p->alarm.repeat_mode_set = set_alarm_repeat_mode;
    m41t6x_p->alarm.flag_get = get_rtc_af;
    m41t6x_p->alarm.set = set_alarm;
    m41t6x_p->alarm.get = get_alarm;
    m41t6x_p->alarm.delete = alarm_delete;

    m41t6x_p->sqw.get = get_rtc_sqw;
    m41t6x_p->sqw.set = set_rtc_sqw;
    m41t6x_p->sqw.status_get = get_sqw_status;
    m41t6x_p->sqw.open = sqw_open;
    m41t6x_p->sqw.close = sqw_close;

    m41t6x_p->i2c.write = write;
    m41t6x_p->i2c.read = read;

    return ESP_OK;
}

/*******************************************************************************
 * Function Name : m41t6x_deinit
 * Function      : deinit m41t6x, free dhandle
 * Input         : [m41t6x_handle *dhandle]
 *                  * dhandle:       m41t6x handle, point to m41t6x_t
 *                  * i2c_dev_lst:   i2c devices list
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_deinit(m41t6x_handle *dhandle)
{
    if (dhandle == NULL)
    {
        return ESP_ERR_INVALID_ARG;
    }
    if (*dhandle == NULL)
    {
        return ESP_ERR_INVALID_ARG;
    }
    free(*dhandle);
    *dhandle = NULL;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_rtc_time
 * Function      : set time to m41t6x
 * Input         : [rtc_time_t *time]
 *                  * time:  target time
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_rtc_time(rtc_time_t *time)
{
    // check argument
    if ((time->centisecond > 99) || (time->second > 59) || (time->minute > 59) || (time->hour > 23) || (time->day == 0) || (time->day > 7) || (time->date == 0) || (time->date > 31) || (time->month == 0) || (time->month > 12) || ((time->year / 100 % 4) > 3) || (time->year % 100) > 99)
    {
        ESP_LOGE(TAG, "read invalid argument: reg: %02XH", REG_TIME_ADR);
        return ESP_ERR_INVALID_ARG;
    }
    memcpy(&m41t6x_p->time, time, sizeof(rtc_time_t));
    byte_t reg_time[8];
    esp_err_t ret = m41t6x_read_regs(REG_TIME_ADR, reg_time, 8);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_TIME_ADR);
        return ret;
    }
    uint8_t century = (time->year / 100 % 4) << 6;
    reg_time[0] = dec_to_bcd8(m41t6x_p->time.centisecond);
    reg_time[1] = (reg_time[1] & bit(7)) | dec_to_bcd8(m41t6x_p->time.second);
    reg_time[2] = (reg_time[2] & bit(7)) | dec_to_bcd8(m41t6x_p->time.minute);
    reg_time[3] = dec_to_bcd8(m41t6x_p->time.hour);
    reg_time[4] = (reg_time[4] & 0xF0) | dec_to_bcd8(m41t6x_p->time.day);
    reg_time[5] = dec_to_bcd8(m41t6x_p->time.date);
    reg_time[6] = dec_to_bcd8(century | m41t6x_p->time.month);
    reg_time[7] = dec_to_bcd8((m41t6x_p->time.year) % 100);
    ret = m41t6x_write_regs(REG_TIME_ADR, reg_time, 8);
    return ret;
}

/*******************************************************************************
 * Function Name : get_rtc_time
 * Function      : get time from m41t6x
 * Input         : [rtc_time_t *time]
 *                  * time:  time of the m41t6x RTC module
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_rtc_time(rtc_time_t *time)
{
    byte_t reg_time[8];
    esp_err_t ret = m41t6x_read_regs(REG_TIME_ADR, reg_time, 8);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_TIME_ADR);
        return ret;
    }
    time->centisecond = bcd8_to_dec(reg_time[0]);
    time->second = bcd8_to_dec(reg_time[1] & 0x7F);
    time->minute = bcd8_to_dec(reg_time[2] & 0x7F);
    time->hour = bcd8_to_dec(reg_time[3]);
    time->day = bcd8_to_dec(reg_time[4] & 0x0F);
    time->date = bcd8_to_dec(reg_time[5]);
    time->month = bcd8_to_dec(reg_time[6] & 0x1F);
    time->year = (uint16_t)bcd8_to_dec(reg_time[7]);
    uint8_t century = bcd8_to_dec(reg_time[6] & 0xC0);
    if (century == M41T6X_CENTURY_0)
    {
        time->year = time->year + 2000;
    }
    else if (century == M41T6X_CENTURY_1)
    {
        time->year = time->year + 2100;
    }
    else if (century == M41T6X_CENTURY_2)
    {
        time->year = time->year + 2200;
    }
    else if (century == M41T6X_CENTURY_3)
    {
        time->year = time->year + 2300;
    }
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : sync_time
 * Function      : sync time to m41t6x
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t sync_time(void)
{
    rtc_time_t *time = malloc(sizeof(rtc_time_t));
    esp_err_t ret = get_rtc_time(time);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_TIME_ADR);
        return ret;
    }
    memcpy(&m41t6x_p->time, time, sizeof(rtc_time_t));
    free(time);
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : m41t6x_crystal
 * Function      : controls the crystal oscillator of the M41T6X RTC module
 * Input         : [bit_t status]
 *                  * status:  Determines whether to enable or disable the crystal
 *                            1 to enable, 0 to disable
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_crystal(bit_t status)
{
    byte_t reg_st;
    esp_err_t ret = m41t6x_read_regs(REG_ST_ADR, &reg_st, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_ST_ADR);
        return ret;
    }
    // default is close crystal
    reg_st |= M41CMD_STOP;
    if (status)
    {
        // set stop bit as 0 to start crystal
        reg_st &= ~M41CMD_STOP;
    }
    ret = m41t6x_write_regs(REG_ST_ADR, &reg_st, 1);
    return ret;
}

/*******************************************************************************
 * Function Name : m41t6x_run
 * Function      : start the crystal oscillator of the m41t6x RTC module
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_run(void)
{
    // set st 0
    return m41t6x_crystal(true);
}

/*******************************************************************************
 * Function Name : m41t6x_stop
 * Function      : stop the crystal oscillator of the m41t6x RTC module
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_stop(void)
{
    // set st 1
    return m41t6x_crystal(false);
}

/*******************************************************************************
 * Function Name : m41t6x_status_get
 * Function      : get the status of the m41t6x RTC module's oscillator
 * Input         : [bit_t *status]
 *                 * status:  run: 1, stop: 0
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_status_get(bit_t *status)
{
    byte_t reg_st;
    esp_err_t ret = m41t6x_read_regs(REG_ST_ADR, &reg_st, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_ST_ADR);
        return ret;
    }
    // run back 1, stop back 0
    *status = reg_st & M41CMD_STOP ? false : true;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : m41t6x_oscillator_enable_set
 * Function      : Enable or disable the oscillator in the m41t6x RTC module
 * Input         : [bit_t status]
 *                  * status:  Set to 'true' to enable the oscillator or 'false' to disable it
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_oscillator_enable_set(bit_t status)
{
    byte_t reg_st;
    esp_err_t ret = m41t6x_read_regs(REG_OFIE_ADR, &reg_st, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_OFIE_ADR);
        return ret;
    }
    // default is close oscillator
    reg_st |= M41CMD_OSC_STOP;
    if (status)
    {
        // set stop bit as 0 to start oscillator
        reg_st &= ~M41CMD_OSC_STOP;
    }
    ret = m41t6x_write_regs(REG_OFIE_ADR, &reg_st, 1);
    return ret;
}

/*******************************************************************************
 * Function Name : m41t6x_oscillator_enable
 * Function      : Enable the oscillator in the m41t6x RTC module, set ofie to 1
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_oscillator_enable()
{
    return m41t6x_oscillator_enable_set(true);
}

/*******************************************************************************
 * Function Name : m41t6x_oscillator_disable
 * Function      : disable the oscillator in the m41t6x RTC module, set ofie to 0
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_oscillator_disable()
{
    return m41t6x_oscillator_enable_set(false);
}

/*******************************************************************************
 * Function Name : m41t6x_oscillator_enable_get
 * Function      : get the current enable status of the oscillator in the m41t6x RTC module
 * Input         : [bit_t *status]
 *                  * status:  enable: true, disable: false
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_oscillator_enable_get(bit_t *status)
{
    byte_t reg_st;
    esp_err_t ret = m41t6x_read_regs(REG_OFIE_ADR, &reg_st, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_OFIE_ADR);
        return ret;
    }
    *status = reg_st & M41CMD_STOP ? false : true;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_rtc_of
 * Function      : set the oscillator flag bit(OF) in the Real-Time Clock (RTC) module m41t6x
 * Input         : [bit_t of]
 * Return        : esp_err_t
 * Notes         : The oscillator must start and have run for at least 4 seconds before attempting to reset the OF bit to '0'
 *******************************************************************************/
esp_err_t set_rtc_of(bit_t of)
{
    byte_t reg_of;
    esp_err_t ret = m41t6x_read_regs(REG_OF_ADR, &reg_of, 1);

    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_OF_ADR);
        return ret;
    }
    if (of)
    {
        reg_of |= M41T6X_OF_SET;
    }
    else
    {
        reg_of &= ~M41T6X_OF_SET;
    }

    ret = m41t6x_write_regs(REG_OF_ADR, &reg_of, 1);

    return ret;
}

/*******************************************************************************
 * Function Name : get_oscillator_status
 * Function      : get the oscillator flag bit(OF) in the Real-Time Clock (RTC) module m41t6x
 * Input         : [bit_t *status]
 *                  * status:  run true, stop false
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_oscillator_status(bit_t *status)
{
    byte_t reg_of;
    esp_err_t ret = m41t6x_read_regs(REG_OF_ADR, &reg_of, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error: %s, reg: %02XH", esp_err_to_name(ret), REG_OF_ADR);
        return ret;
    }
    // if of == 1, status = false, stop
    printf("  reg_of: %02x\n", reg_of);
    *status = (reg_of & bit(2)) ? false : true;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_rtc_sqw
 * Function      : set the square wave output frequency
 * Input         : [m41t6x_sqw_t sqw]
 *                  * sqw: square wave output frequency setting
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_rtc_sqw(m41t6x_sqw_t sqw)
{
    byte_t reg_st;
    esp_err_t ret = m41t6x_read_regs(REG_SQW_ADR, &reg_st, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_SQW_ADR);
        return ret;
    }
    // default Close square wave output
    reg_st &= M41T6X_SQW_SET;
    // check sqw if valid
    bool flag = false;
    for (int i = M41T6X_SQW_NONE; i < M41T6X_SQW_1_HZ; i += 0x10)
    {
        if (sqw == i)
        {
            reg_st |= sqw;
            flag = true;
            break;
        }
    }
    if (!flag)
    {
        ESP_LOGE(TAG, "read invalid argument, reg: %02XH", REG_SQW_ADR);
        return ESP_ERR_INVALID_ARG;
    }
    ret = m41t6x_write_regs(REG_SQW_ADR, &reg_st, 1);
    return ret;
}

/*******************************************************************************
 * Function Name : get_rtc_sqw
 * Function      : get the square wave output frequency
 * Input         : [m41t6x_sqw_t *sqw]
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_rtc_sqw(m41t6x_sqw_t *sqw)
{
    byte_t reg_st;
    esp_err_t ret = m41t6x_read_regs(REG_SQW_ADR, &reg_st, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_SQW_ADR);
        return ret;
    }
    *sqw = (m41t6x_sqw_t)(reg_st & M41T6X_SQW_GET);
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_sqwe
 * Function      : set the square wave enable or disable
 * Input         : [bit_t sqwe]
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_sqwe(bit_t sqwe)
{
    byte_t reg_sqwe;
    esp_err_t ret = m41t6x_read_regs(REG_SQWE_ADR, &reg_sqwe, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_SQW_ADR);
        return ret;
    }
    if (sqwe)
    {
        reg_sqwe |= M41T6X_SQWE_SET;
    }
    else
    {
        reg_sqwe &= ~M41T6X_SQWE_SET;
    }
    ret = m41t6x_write_regs(REG_SQWE_ADR, &reg_sqwe, 1);
    return ret;
}

/*******************************************************************************
 * Function Name : sqw_open
 * Function      : open the square wave pin
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : set sqwe true
 *******************************************************************************/
esp_err_t sqw_open(void)
{
    return set_sqwe(true);
}

/*******************************************************************************
 * Function Name : sqw_close
 * Function      : close the square wave pin
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : set sqwe false
 *******************************************************************************/
esp_err_t sqw_close(void)
{
    return set_sqwe(false);
}

/*******************************************************************************
 * Function Name : get_sqw_status
 * Function      : get the square wave pin status
 * Input         : [bit_t *sqwe]
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_sqw_status(bit_t *sqwe)
{
    byte_t reg_sqwe;
    esp_err_t ret = m41t6x_read_regs(REG_SQWE_ADR, &reg_sqwe, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_SQWE_ADR);
        return ret;
    }
    *sqwe = (reg_sqwe & bit(6)) ? true : false;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_output_level
 * Function      : set output level
 * Input         : [bit_t out_level]
 *                * out_level: high level: true, low level: false
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_output_level(bit_t out_level)
{
    byte_t reg_out;
    esp_err_t ret = m41t6x_read_regs(REG_OUTPUT_ADR, &reg_out, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_SQW_ADR);
        return ret;
    }
    // default is high level
    reg_out |= M41T6X_OUT;
    if (!out_level)
    {
        // high: 1,  low: 0
        reg_out &= ~M41T6X_OUT;
    }
    ret = m41t6x_write_regs(REG_OUTPUT_ADR, &reg_out, 1);
    return ret;
}

/*******************************************************************************
 * Function Name : get_output_level
 * Function      : get output level
 * Input         : [bit_t *out_level]
 *                  * out_level: high level: true, low level: false
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_output_level(bit_t *out_level)
{
    byte_t reg_out;
    esp_err_t ret = m41t6x_read_regs(REG_OUTPUT_ADR, &reg_out, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error,  err: %s, reg: %02XH", esp_err_to_name(ret), REG_OUTPUT_ADR);
        return ret;
    }
    *out_level = reg_out & M41T6X_OUT ? true : false;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_rtc_calibration
 * Function      : set calibration
 * Input         : [uint8_t calibration]
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_rtc_calibration(uint8_t calibration)
{
    // check argument valid
    if ((calibration <= 0x1F) || (calibration >= 0x80 && calibration <= 0x9F))
    {
        byte_t reg_calibration;
        esp_err_t ret = m41t6x_read_regs(REG_CALIBRATION_ADR, &reg_calibration, 1);
        if (ret != ESP_OK)
        {
            ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_CALIBRATION_ADR);
            return ret;
        }
        // default is 0
        reg_calibration &= M41T6X_CALIBRATION;
        // calibration
        calibration = ((calibration & bit(7)) ? bit(5) : 0) | (calibration & 0x1F);
        reg_calibration = reg_calibration | calibration;
        ret = m41t6x_write_regs(REG_CALIBRATION_ADR, &reg_calibration, 1);
        return ret;
    }
    else
    {
        ESP_LOGE(TAG, "read invalid argument: reg: %02XH", REG_CALIBRATION_ADR);
        return ESP_ERR_INVALID_ARG;
    }
}

/*******************************************************************************
 * Function Name : get_rtc_calibration
 * Function      : get calibration
 * Input         : [uint8_t *calibration]
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_rtc_calibration(uint8_t *calibration)
{
    byte_t reg_calibration;
    esp_err_t ret = m41t6x_read_regs(REG_CALIBRATION_ADR, &reg_calibration, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_CALIBRATION_ADR);
        return ret;
    }
    uint8_t temp = reg_calibration & 0x3F;
    *calibration = (temp & bit(5) ? bit(7) : 0) | (temp & 0x1F);
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_wtd
 * Function      : set watchdog multiplier and watchdog resolution
 * Input         : [uint8_t wtd_mul, wtd_res_t wtd]
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_wtd(uint8_t wtd_mul, wtd_res_t wtd)
{
    byte_t reg_wtd;
    wtd_mul = wtd_mul << 2;
    if (wtd == 0x83)
    {
        reg_wtd = 0x00;
    }
    else if (wtd == M41T6X_WATCHDOG_16_HZ || wtd == M41T6X_WATCHDOG_4_HZ || wtd == M41T6X_WATCHDOG_1_HZ || wtd == M41T6X_WATCHDOG_1_4_HZ || wtd == M41T6X_WATCHDOG_1_60_HZ)
    {
        reg_wtd = wtd_mul | wtd;
    }
    else
    {
        ESP_LOGE(TAG, "read invalid argument: reg: %02XH", REG_WTD_ADR);
        return ESP_ERR_INVALID_ARG;
    }
    esp_err_t ret = m41t6x_write_regs(REG_WTD_ADR, &reg_wtd, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error: err: %s, reg: %02XH", esp_err_to_name(ret), REG_WTD_ADR);
        return ret;
    }
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : get_wtd
 * Function      : get watchdog multiplier and watchdog resolution
 * Input         : [uint8_t *wtd_mul, wtd_res_t *wtd]
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_wtd(uint8_t *wtd_mul, wtd_res_t *wtd)
{
    byte_t reg_wtd;
    esp_err_t ret = m41t6x_read_regs(REG_WTD_ADR, &reg_wtd, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_WTD_ADR);
        return ret;
    }
    *wtd_mul = (reg_wtd >> 2) & 0x1F;
    *wtd = reg_wtd & 0x83;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : get_wtd_status
 * Function      : check wtd if overtime
 * Input         : [bit_t *wdf]
 *                  * wdf: overtime: 1, not overtime: 0
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_wtd_status(bit_t *wdf)
{
    byte_t reg_wdf;
    esp_err_t ret = m41t6x_read_regs(REG_WDF_ADR, &reg_wdf, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error: %s, reg: %02XH", esp_err_to_name(ret), REG_WDF_ADR);
        return ret;
    }
    *wdf = (reg_wdf & bit(7)) ? true : false;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_rtc_afe
 * Function      : set alarm flag enable or disable
 * Input         : [bit_t afe]
 *                  * afe: alarm flag enable bit
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_rtc_afe(bit_t afe)
{
    byte_t reg_afe;
    esp_err_t ret = m41t6x_read_regs(REG_AFE_ADR, &reg_afe, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_AFE_ADR);
        return ret;
    }
    if (afe)
    {
        reg_afe |= M41T6X_AFE_SET;
    }
    else
    {
        reg_afe &= M41T6X_AFE_SET;
    }
    ret = m41t6x_write_regs(REG_AFE_ADR, &reg_afe, 1);
    return ret;
}

/*******************************************************************************
 * Function Name : enable_af
 * Function      : enable alarm flag
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t enable_af(void)
{
    return set_rtc_afe(true);
}

/*******************************************************************************
 * Function Name : disable_af
 * Function      : disable alarm flag
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t disable_af(void)
{
    return set_rtc_afe(false);
}

/*******************************************************************************
 * Function Name : get_rtc_afe
 * Function      : get alarm flag enable or disable
 * Input         : [bit_t *afe]
 *                  * afe: alarm flag enable bit, enable 1, disable 0
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_rtc_afe(bit_t *afe)
{
    byte_t reg_afe;
    esp_err_t ret = m41t6x_read_regs(REG_AFE_ADR, &reg_afe, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_AFE_ADR);
        return ret;
    }
    *afe = (reg_afe & bit(7)) ? true : false;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_alarm_repeat_mode
 * Function      : set alarm repeat mode
 * Input         : [m41t6x_rpt_t rpt]
 *                  * rpt: alarm repeat mode
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_alarm_repeat_mode(m41t6x_rpt_t rpt)
{
    byte_t reg_rpt[4];
    esp_err_t ret = m41t6x_read_regs(REG_RPT_ADR, reg_rpt, 4);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_RPT_ADR);
        return ret;
    }
    reg_rpt[0] = (reg_rpt[0] & 0x3F) | (rpt << 6);
    reg_rpt[1] = (reg_rpt[1] & 0x3F) | (reg_rpt[1] & bit(2)) ? bit(7) : 0;
    reg_rpt[2] = (reg_rpt[2] & 0x7F) | (reg_rpt[2] & bit(3)) ? bit(7) : 0;
    reg_rpt[3] = (reg_rpt[3] & 0x7F) | (reg_rpt[3] & bit(4)) ? bit(7) : 0;
    ret = m41t6x_write_regs(REG_RPT_ADR, reg_rpt, 4);
    return ret;
}

/*******************************************************************************
 * Function Name : get_alarm_repeat_mode
 * Function      : get alarm repeat mode
 * Input         : [m41t6x_rpt_t *rpt]
 *                  * rpt: alarm repeat mode
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_alarm_repeat_mode(m41t6x_rpt_t *rpt)
{
    uint8_t data_rd[4];
    esp_err_t ret = m41t6x_read_regs(REG_RPT_ADR, data_rd, 4);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_RPT_ADR);
        return ret;
    }
    uint8_t rpt_4_5 = (data_rd[0] & 0xC0) >> 6;
    uint8_t rpt_3 = (data_rd[1] & bit(7)) ? bit(2) : 0;
    uint8_t rpt_2 = (data_rd[2] & bit(7)) ? bit(3) : 0;
    uint8_t rpt_1 = (data_rd[3] & bit(7)) ? bit(4) : 0;
    *rpt = rpt_4_5 | rpt_3 | rpt_2 | rpt_1;
    return ret;
}

/*******************************************************************************
 * Function Name : get_rtc_af
 * Function      : get alarm if overtime
 * Input         : [bit_t *af]
 *                 * af: check alarm overtime, overtime 1, not overtime 0
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_rtc_af(bit_t *af)
{
    byte_t reg_wdf;
    esp_err_t ret = m41t6x_read_regs(REG_AF_ADR, &reg_wdf, 1);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error: %s, reg: %02XH", esp_err_to_name(ret), REG_AF_ADR);
        return ret;
    }
    *af = (reg_wdf & bit(6)) ? true : false;
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : set_alarm
 * Function      : set alarm
 * Input         : [rtc_time_t alarm]
 *                  * alarm: alarm
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t set_alarm(rtc_time_t alarm)
{
    if (
        alarm.month == 0 || (alarm.month) > 12 ||
        alarm.date == 0 || (alarm.date) > 31 ||
        alarm.hour > 23 ||
        alarm.minute > 59 ||
        alarm.second > 59)
    {
        ESP_LOGE(TAG, "read invalid argument: reg: %02XH", REG_ALARM_ADR);
        return ESP_ERR_INVALID_ARG;
    }
    byte_t reg_alarm[5];
    esp_err_t ret = m41t6x_read_regs(REG_ALARM_ADR, reg_alarm, 5);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_ALARM_ADR);
        return ret;
    }
    reg_alarm[0] = (reg_alarm[0] & 0xC0) | dec_to_bcd8(alarm.month);
    reg_alarm[1] = (reg_alarm[1] & 0xC0) | dec_to_bcd8(alarm.date);
    reg_alarm[2] = (reg_alarm[2] & bit(7)) | dec_to_bcd8(alarm.hour);
    reg_alarm[3] = (reg_alarm[3] & bit(7)) | dec_to_bcd8(alarm.minute);
    reg_alarm[4] = (reg_alarm[3] & bit(7)) | dec_to_bcd8(alarm.second);
    ret = m41t6x_write_regs(REG_ALARM_ADR, reg_alarm, 5);
    return ret;
}

/*******************************************************************************
 * Function Name : get_alarm
 * Function      : get alarm
 * Input         : [rtc_time_t *alarm]
 *                  * alarm: alarm
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t get_alarm(rtc_time_t *alarm)
{
    byte_t reg_alarm[5];
    esp_err_t ret = m41t6x_read_regs(REG_ALARM_ADR, reg_alarm, 5);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg error, err: %s, reg: %02XH", esp_err_to_name(ret), REG_ALARM_ADR);
        return ret;
    }
    alarm->month = bcd8_to_dec(reg_alarm[0] & 0x1F);
    printf("month: %02x\n", alarm->month);
    alarm->date = bcd8_to_dec(reg_alarm[1] & 0x3F);
    alarm->hour = bcd8_to_dec(reg_alarm[2] & 0x3F);
    alarm->minute = bcd8_to_dec(reg_alarm[3] & 0x7F);
    alarm->second = bcd8_to_dec(reg_alarm[4] & 0x7F);
    return ESP_OK;
}

/*******************************************************************************
 * Function Name : alarm_delete
 * Function      : make alarm to be disabled
 * Input         : NULL
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t alarm_delete(void)
{
    // write '0' to the alarm date register and to RPT5–RPT1
    rtc_time_t alarm;
    alarm.month = 0x00;
    alarm.date = 0x00;
    alarm.hour = 0x00;
    alarm.minute = 0x00;
    alarm.second = 0x00;
    esp_err_t ret1 = set_alarm(alarm);
    esp_err_t ret2 = set_alarm_repeat_mode(M41T6X_RPT_YEAR);
    return ret1 != ESP_OK ? ret1 : (ret2 != ESP_OK ? ret2 : ESP_OK);
}

/*******************************************************************************
 * Function Name : bcd8_to_dec
 * Function      : Converts an 8-bit binary-coded decimal (BCD) to decimal
 * Input         : [bcd8_t bcd]
 *                * bcd: The BCD value to convert
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
uint8_t bcd8_to_dec(bcd8_t bcd)
{
    return (bcd >> 4) * 10 + (bcd & 0x0F);
}

/*******************************************************************************
 * Function Name : dec_to_bcd8
 * Function      : Converts an 8-bit decimal number to binary-coded decimal (BCD)
 * Input         : [uint8_t dec]
 *                * bcd: The BCD value to convert
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
uint8_t dec_to_bcd8(uint8_t dec)
{
    return ((dec / 10) << 4) | (dec % 10);
}

/*******************************************************************************
 * Function Name : m41t6x_read_regs
 * Function      : Reads data from the specified registers of the M41T6X RTC device
 * Input         : [uint8_t reg_adr, byte_t *out_buf, size_t dlen]
 *                * reg_adr: The register address to start reading from
 *                * out_buf: Pointer to a buffer where the read data will be stored
 *                * dlen:    The length of bytes to read
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_read_regs(uint8_t reg_adr, byte_t *out_buf, size_t dlen)
{
    if (m41t6x_p != NULL)
    {
        if (m41t6x_p->i2c.read != NULL)
        {
            return m41t6x_p->i2c.read(reg_adr, out_buf, dlen);
        }
        else
        {
            ESP_LOGE(TAG, "i2c read function is not set");
        }
    }

    return ESP_ERR_INVALID_STATE;
    // return i2c_master_transmit_receive(m41t6x_p->i2c_dev->i2c_handle, &reg_adr, 1, out_buf, dlen, 1000);
}

/*******************************************************************************
 * Function Name : m41t6x_write_regs
 * Function      : Writes data to the specified registers of the M41T6X RTC device
 * Input         : [uint8_t reg_adr, byte_t *in_buf, size_t dlen]
 *                * reg_adr: The register address to start reading from
 *                * in_buf:  Pointer to a buffer where the read data will be stored
 *                * dlen:    The length of bytes to read
 * Return        : esp_err_t
 * Notes         : None
 *******************************************************************************/
esp_err_t m41t6x_write_regs(uint8_t reg_adr, byte_t *in_buf, size_t dlen)
{
    if (m41t6x_p != NULL)
    {
        if (m41t6x_p->i2c.write != NULL)
        {
            return m41t6x_p->i2c.write(reg_adr, in_buf, dlen);
        }
        else
        {
            ESP_LOGE(TAG, "i2c write function is not set");
        }
    }

    return ESP_ERR_INVALID_STATE;

    // byte_t *bufx = malloc(dlen + 1);
    // if (bufx == NULL)
    // {
    //     ESP_LOGE(TAG, "NO memory when write to device");
    //     return ESP_ERR_NO_MEM;
    // }
    // bufx[0] = reg_adr;
    // memcpy(&bufx[1], in_buf, dlen);
    // esp_err_t ret = i2c_master_transmit(m41t6x_p->i2c_dev->i2c_handle, bufx, dlen + 1, 1000);
    // if (ret != ESP_OK)
    // {
    //     ESP_LOGE(TAG, "Write to device error, err: %s, reg: %02XH", esp_err_to_name(ret), reg_adr);
    // }
    // free(bufx);
    // return ret;
}
